/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package pasta;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Vector;
import java.util.List;
import java.net.*;

import rice.p2p.past.*;
import rice.Continuation;
import rice.environment.Environment;
import rice.p2p.commonapi.Id;
import rice.pastry.*;
import rice.pastry.commonapi.PastryIdFactory;
import rice.pastry.socket.SocketPastryNodeFactory;
import rice.pastry.standard.RandomNodeIdFactory;
import rice.persistence.*;

import com.thoughtworks.xstream.XStream;

/**
 *
 * @author Junior
 */
public class PastApp {

    PastApp () {
        musics = new ArrayList<Music>();
        myDownloadedFiles = new ArrayList<Music>();
        splitLength = 256 * 1024;
        env = new Environment();
        env.getParameters().setString("nat_search_policy","never");
    }

    public void boot (String bootPort, String address, String bindPort ) throws Exception {

        int intBootPort = Integer.parseInt(bootPort);
        InetAddress bootAddr = InetAddress.getByName(address);
        int intBindPort = Integer.parseInt(bindPort);

        InetSocketAddress bootAddress = new InetSocketAddress(bootAddr, intBootPort);

        bootstrap(intBindPort, bootAddress);

        if (intBootPort == intBindPort) {
            insertCatalogIntoPast();
        }

    }

    public void bootstrap (int bindport, InetSocketAddress bootaddress) throws Exception {
        // Generate the NodeIds Randomly
         NodeIdFactory nidFactory = new RandomNodeIdFactory(env);

        // construct the PastryNodeFactory, this is how we use rice.pastry.socket
         PastryNodeFactory factory = new SocketPastryNodeFactory(nidFactory, bindport, env);

         PastryNode node = factory.newNode();

        // used for generating PastContent object Ids.
        // this implements the "hash function" for our DHT
        idf = new rice.pastry.commonapi.PastryIdFactory(env);

        // create a different storage root for each node
        storageDirectory = "./past/"+node.getId().hashCode();

        // create the persistent part
        Storage stor = new PersistentStorage(idf, storageDirectory, 100 * 1024 * 1024, node.getEnvironment());
        past = new PastImpl(node, new StorageManagerImpl(idf, stor, new LRUCache( new MemoryStorage(idf),0 , node.getEnvironment())), 2, "");

        node.boot(bootaddress);

        // the node may require sending several messages to fully boot into the ring
        synchronized(node) {
            while(!node.isReady() && !node.joinFailed()) {
                // delay so we don't busy-wait
                node.wait(500);

		// abort if can't join
                if (node.joinFailed()) {
                    throw new IOException("Could not join the FreePastry ring.  Reason:"+node.joinFailedReason());
                }
            }
        }
    }

    public void insertCatalogIntoPast() {
        XStream xstream = new XStream();
	String catalogXml = xstream.toXML(musics);
        
        byte[] catalog = catalogXml.getBytes();

        /* cant be final to allow mutability, ie update to data */
        PastContent catalogContent = new Fragment(idf.buildId("catalog"), catalog);

        past.insert(catalogContent, new Continuation<Boolean[], Exception>() {
            public void receiveResult(Boolean[] results) {
            }

            public void receiveException(Exception result) {
                System.out.println("Error storing Catalog");
                result.printStackTrace();
            }
        });
    }

    public void retrieveCatalogFromPast() {
        final Id lookupKey = idf.buildId("catalog");

        past.lookup(lookupKey, new Continuation<PastContent, Exception>() {
            public void receiveResult(PastContent result) {
                Fragment fragment = (Fragment)result;
                String catalogXml = new String(fragment.getContent());

                XStream xstream = new XStream();
                musics = (ArrayList) xstream.fromXML(catalogXml);
            }

            public void receiveException(Exception result) {
                System.out.println("Error looking up "+lookupKey);
                result.printStackTrace();
            }
        });
    }

    public ArrayList<Id> insertDataIntoPast(Vector<byte[]> bytesArray) throws FileNotFoundException, IOException{

        Iterator iterator = bytesArray.iterator();
        ArrayList<Id> idArrayList = new ArrayList<Id>();

        while (iterator.hasNext() ){

            byte[] bytes = (byte[]) iterator.next();
            final PastContent dataContent = new Fragment(idf.buildId(bytes), bytes);

            past.insert(dataContent, new Continuation<Boolean[], Exception>() {
                public void receiveResult(Boolean[] results) {
                }

                public void receiveException(Exception result) {
                    System.out.println("Error storing " + dataContent);
                    result.printStackTrace();
                }
            });
            idArrayList.add(dataContent.getId());
        }
        return idArrayList;
    }

    public ArrayList<Music> searchDataIntoPast(String searchKey) throws InterruptedException{
        retrieveCatalogFromPast();
        env.getTimeSource().sleep(1000);

        Iterator musicItr = musics.iterator();
        searchResult = new ArrayList<Music>();

        while (musicItr.hasNext()) {
            Music music = (Music) musicItr.next();
            if (music.getTitle().contains(searchKey)) {
                searchResult.add(music);
            }

            else if (music.getArtist().contains(searchKey)) {
                searchResult.add(music);
            }

            else if (music.getAlbum().contains(searchKey)) {
                searchResult.add(music);
            }

            else if (music.getGender().contains(searchKey)) {
                searchResult.add(music);
            }
        }

        return searchResult;
    }

    public void retrieveDataFromPast(ArrayList<Music> filesToDownload) throws IOException, InterruptedException {

        Iterator musicItr = filesToDownload.iterator();

        int index = 0;
        while (musicItr.hasNext()) {
            Music music = (Music) musicItr.next();
            System.out.println(music.print());

            ArrayList Ids = (ArrayList) music.getFragmentsIds();

            Iterator idIterator = Ids.iterator();

            final Vector<byte[]> bytes = new Vector<byte[]>();

            while (idIterator.hasNext()) {

                final Id lookupKey = (Id)idIterator.next();

                past.lookup(lookupKey, new Continuation<PastContent, Exception>() {

                    public void receiveResult(PastContent result) {
                        Fragment fragment = (Fragment)result;
                        bytes.add(fragment.getContent());
                    }

                    public void receiveException(Exception result) {
                        System.out.println("Error looking up " + lookupKey);
                        result.printStackTrace();
                    }
                    });

                env.getTimeSource().sleep(1000);
            }
            env.getTimeSource().sleep(5000);
            Merger mergeFile = new Merger(music.getTitle(),bytes, storageDirectory);
            myDownloadedFiles.add(music);
        }
    }

    public void insertMusicIntoPast (String title, String artist, String album, String gender, String path) throws IOException {
        Vector<byte[]> bytesArray = new Vector<byte[]>();

        retrieveCatalogFromPast();

        Music music = new Music (title, artist, album, gender);
        Splitter splitter = new Splitter ();

        bytesArray = splitter.takeInput(path, splitLength);

        music.setFragmentsIds(insertDataIntoPast(bytesArray));

        musics.add (music);

        insertCatalogIntoPast();
    }

    public void retrieveMusicsFromPast (int[] selectedFiles) throws IOException, InterruptedException {
        //retrieveCatalogFromPast();
        env.getTimeSource().sleep(1000);
        ArrayList<Music> filesToDownload = new ArrayList<Music>();

        int numberOfFilesToDownload = 0;

        while(numberOfFilesToDownload < selectedFiles.length){
            filesToDownload.add(searchResult.get(selectedFiles[numberOfFilesToDownload]));
            numberOfFilesToDownload++;
        }
        
        retrieveDataFromPast(filesToDownload);
     }

    public ArrayList<Music> getSearchResultArray() {
        return searchResult;
    }

    public ArrayList<Music> getMyDownloadedFiles() {
        return myDownloadedFiles;
    }

    //Begin Attributes Section
    private List<Music> musics;
    private ArrayList<Music> searchResult;
    private ArrayList<Music> myDownloadedFiles;
    private Past past;
    private Environment env;
    private int splitLength;
    private PastryIdFactory idf;
    private String storageDirectory;
    //End

}
